@using Radzen
@implements IDisposable
<li class="rz-treenode">
    <div class="@($"rz-treenode-content {(selected ? "rz-treenode-content-selected" : "")}")" @onclick="@Select">
        @if (ChildContent != null || HasChildren)
        {
            <span class="@($"rz-tree-toggler rzi {(expanded ? "rzi-caret-down" : "rzi-caret-right")}")" @onclick="@Toggle" @onclick:stopPropagation></span>
        }
        @if(Tree != null && Tree.AllowCheckBoxes)
        {
            <RadzenCheckBox TValue="bool?" Value="@IsChecked()" Change="@CheckedChange" Style="@(ParentItem != null && !HasChildren ? "margin-left:25px;margin-right:5px;" : "margin-right:5px;")" />
        }
        @if (Template != null)
        {
            @Template(this)
        } else
        {
            <span class="rz-treenode-label">@Text</span>
        }
    </div>
    @if (ChildContent != null && expanded)
    {
    <ul class="rz-treenode-children">
        @ChildContent
    </ul>
    }
</li>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public RenderFragment<RadzenTreeItem> Template { get; set; }

    [Parameter]
    public string Text { get; set; }

    private bool expanded;

    [Parameter]
    public bool Expanded { get; set; }

    [Parameter]
    public object Value { get; set; }

    [Parameter]
    public bool HasChildren { get; set; }

    private bool selected;

    [Parameter]
    public bool Selected { get; set; }

    [CascadingParameter]
    public RadzenTree Tree { get; set; }

    RadzenTreeItem _parentItem;

    [Parameter]
    public RadzenTreeItem ParentItem
    {
        get
        {
            return _parentItem;
        }
        set
        {
            if (_parentItem != value)
            {
                _parentItem = value;

                if (_parentItem != null)
                {
                    _parentItem.AddItem(this);
                }
            }
        }
    }

    internal List<RadzenTreeItem> items = new List<RadzenTreeItem>();

    internal void AddItem(RadzenTreeItem item)
    {
        if (items.IndexOf(item) == -1)
        {
            items.Add(item);
        }
    }

    internal void RemoveItem(RadzenTreeItem item)
    {
        if (items.IndexOf(item) != -1)
        {
            items.Remove(item);
        }
    }

    public void Dispose()
    {
        if (ParentItem != null)
        {
            ParentItem.RemoveItem(this);
        }
        else if(Tree != null)
        {
            Tree.RemoveItem(this);
        }
    }

    async Task Toggle()
    {
        expanded = !expanded;

        if (expanded)
        {
            await Tree?.ExpandItem(this);
        }
    }

    void Select()
    {
        selected = true;
        Tree?.SelectItem(this);
    }

    internal void Unselect()
    {
        selected = false;
        StateHasChanged();
    }

    internal void RenderChildContent(RenderFragment content)
    {
        ChildContent = content;
    }

    override protected void OnInitialized()
    {
        expanded = Expanded;

        if (expanded)
        {
            Tree?.ExpandItem(this);
        }

        selected = Selected;

        if (selected)
        {
            Tree?.SelectItem(this);
        }

        if (Tree != null && ParentItem == null)
        {
            Tree.AddItem(this);
        }
    }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        var shouldExpand = false;

        if (parameters.DidParameterChange(nameof(Expanded), Expanded))
        {
            // The Expanded property has changed - update the expanded state
            expanded = parameters.GetValueOrDefault<bool>(nameof(Expanded));
            shouldExpand = true;
        }

        if (parameters.DidParameterChange(nameof(Value), Value))
        {
            // The Value property has changed - the children may have also changed
            shouldExpand = expanded;
        }

        if (shouldExpand)
        {
            // Either the expanded state or Value changed - expand the node to render its children
            Tree?.ExpandItem(this);
        }

        if (parameters.DidParameterChange(nameof(Selected), Selected))
        {
            selected = parameters.GetValueOrDefault<bool>(nameof(Selected));

            if (selected)
            {
                Tree?.SelectItem(this);
            }
        }

        await base.SetParametersAsync(parameters);
    }

    async Task CheckedChange(bool? value)
    {
        if (Tree != null)
        {
            var checkedValues = GetCheckedValues();

            if (Tree.AllowCheckChildren)
            {
                if (value == true)
                {
                    var valueAndChildren = GetValueAndAllChildValues();
                    checkedValues = checkedValues.Union(valueAndChildren);
                    Tree.SetUncheckedValues(Tree.UncheckedValues.Except(valueAndChildren));
               }
                else
                {
                    var valueAndChildren = GetValueAndAllChildValues();
                    checkedValues = checkedValues.Except(valueAndChildren);
                    Tree.SetUncheckedValues(valueAndChildren.Union(Tree.UncheckedValues));
                }
            }

            if (Tree.AllowCheckParents)
            {
                checkedValues = UpdateCheckedValuesWithParents(checkedValues, value);
            }

            await Tree.SetCheckedValues(checkedValues);
        }
    }

    bool? IsChecked()
    {
        var checkedValues = GetCheckedValues();

        if(HasChildren && IsOneChildUnchecked() && IsOneChildChecked())
        {
            return null;
        }

        return checkedValues.Contains(Value);
    }

    IEnumerable<object> GetCheckedValues()
    {
        return Tree.CheckedValues != null ? Tree.CheckedValues : Enumerable.Empty<object>();
    }

    IEnumerable<object> GetAllChildValues(Func<object, bool> predicate = null)
    {
        var children = items.Concat(items.SelectManyRecursive(i => i.items)).Select(i => i.Value);

        return predicate != null ? children.Where(predicate) : children;
    }

    IEnumerable<object> GetValueAndAllChildValues()
    {
        return new object[] { Value }.Concat(GetAllChildValues());
    }

    bool AreAllChildrenChecked(Func<object, bool> predicate = null)
    {
        var checkedValues = GetCheckedValues();
        return GetAllChildValues(predicate).All(i => checkedValues.Contains(i));
    }

    bool AreAllChildrenUnchecked(Func<object, bool> predicate = null)
    {
        var checkedValues = GetCheckedValues();
        return GetAllChildValues(predicate).All(i => !checkedValues.Contains(i));
    }

    bool IsOneChildUnchecked()
    {
        var checkedValues = GetCheckedValues();
        return GetAllChildValues().Any(i => !checkedValues.Contains(i));
    }

    bool IsOneChildChecked()
    {
        var checkedValues = GetCheckedValues();
        return GetAllChildValues().Any(i => checkedValues.Contains(i));
    }

    IEnumerable<object> UpdateCheckedValuesWithParents(IEnumerable<object> checkedValues, bool? value)
    {
        var p = ParentItem;
        while (p != null)
        {
            if (value == false && p.AreAllChildrenUnchecked(i => !object.Equals(i, Value)))
            {
                checkedValues = checkedValues.Except(new object[] { p.Value });
            }
            else if (value == true && p.AreAllChildrenChecked(i => !object.Equals(i, Value)))
            {
                checkedValues = checkedValues.Union(new object[] { p.Value });
            }

            p = p.ParentItem;
        }

        return checkedValues;
    }
}